// Copyright 2006 Two Pair, LLC. All Rights Reserved.
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;

/**
 * Processes command-line arguments into logical options.
 */
public class ArgumentProcessor {

  /**
   * The default length of a phrase if unspecified by the user.
   */
  private static final int DEFAULT_PHRASE_LENGTH = 1;

  /**
   * The default frequency with with a phrase must occur to be printed if
   * unspecified by the user.
   */
  private static final int DEFAULT_THRESHOLD = 1;

  /**
   * A sentinal value to initialize integer options as unspecified.
   */
  private static final int INT_SENTINAL = -1;

  /**
   * A sentinal value to initialize string options as unspecified.
   */
  private static final String STRING_SENTINAL = null;

  /**
   * Process command-line arguments into logical options.
   * 
   * @param args a set of command-line arguments
   * @return a set of user settings
   * @throws IllegalArgumentException if the user arguments are malformed
   */
  public static UserSettings process(String[] args)
      throws IllegalArgumentException {

    // Initialize all values to sentinal values for duplicate detection
    String inputFileName = STRING_SENTINAL;
    String stopFileName = STRING_SENTINAL;
    String variableBreakChars = STRING_SENTINAL;
    int phraseLength = INT_SENTINAL;
    int threshold = INT_SENTINAL;

    // Process all arguments into locals
    for (int i = 0; i < args.length; ++i) {
      String arg = args[i];
      if (i == args.length - 1) {
        inputFileName = arg;
      } else if ("-b".equals(arg)) {
        assertNotDuplicate(variableBreakChars, arg);
        variableBreakChars = args[++i];
      } else if ("-s".equals(arg)) {
        assertNotDuplicate(stopFileName, arg);
        stopFileName = args[++i];
      } else if ("-t".equals(arg)) {
        assertNotDuplicate(threshold, arg);
        threshold = parsePositiveInt(args[++i], 1, Integer.MAX_VALUE);
      } else if ("-n".equals(arg)) {
        assertNotDuplicate(phraseLength, arg);
        phraseLength = parsePositiveInt(args[++i], 2, 9);
      } else {
        throw new IllegalArgumentException("invalid option '" + arg
            + "'; legal options are -b, -s, -t, and -n");
      }
    }

    // Set up all logical options
    if (phraseLength == INT_SENTINAL) {
      phraseLength = DEFAULT_PHRASE_LENGTH;
    }

    if (threshold == INT_SENTINAL) {
      threshold = DEFAULT_THRESHOLD;
    }

    CharacterAnalyzer cAnalyzer = new CharacterAnalyzer();
    if (variableBreakChars != STRING_SENTINAL) {
      cAnalyzer = new CharacterAnalyzer(variableBreakChars);
    }

    WordAnalyzer wordAnalyzer = new WordAnalyzer();
    if (stopFileName != STRING_SENTINAL) {
      try {
        InputStream stopFileInputStream = FileStreamCreator.createInputStream(stopFileName);
        wordAnalyzer = new WordAnalyzer(cAnalyzer, stopFileInputStream);
      } catch (FileNotFoundException e) {
        throw new IllegalArgumentException("stop file '" + stopFileName
            + "' does not exist");
      } catch (IOException e) {
        throw new IllegalArgumentException("stop file '" + stopFileName
            + "' cannot be read from");
      }
    }

    // Construct and return the logical user settings
    return new UserSettings(inputFileName, cAnalyzer, wordAnalyzer, threshold,
        phraseLength);
  }

  /**
   * Ensure that the value is the integer sentinal value.
   * 
   * @param value the value to be tested
   * @param arg the option being tested
   * @throws IllegalArgumentException if <code>value</code> is not the
   *           sentinal value
   */
  private static void assertNotDuplicate(int value, String arg)
      throws IllegalArgumentException {
    if (value != INT_SENTINAL) {
      throw new IllegalArgumentException("duplicate option '" + arg + "'");
    }
  }

  /**
   * Ensure that the value is the string sentinal value.
   * 
   * @param value the value to be tested
   * @param arg the option being tested
   * @throws IllegalArgumentException if <code>value</code> is not the
   *           sentinal value
   */
  private static void assertNotDuplicate(String value, String arg)
      throws IllegalArgumentException {
    if (value != STRING_SENTINAL) {
      throw new IllegalArgumentException("duplicate option '" + arg + "'");
    }
  }

  /**
   * Parse a string argument as a positive integer.
   * 
   * @param arg the argument being parsed
   * @param min_value the minimum legal value
   * @param max_value the maximum legal value
   * @return the argument as an integer
   * @throws IllegalArgumentException the argument is not a number, or does not
   *           fall between <code>min_value</code> and <code>max_value</code>,
   *           inclusive
   */
  private static int parsePositiveInt(String arg, int min_value, int max_value)
      throws IllegalArgumentException {

    // ASSERT: min_value > 0
    // ASSERT: max_value > 0
    // ASSERT: min_value <= max_value

    try {
      // parse the string to an integer as base ten
      int result = Integer.parseInt(arg, 10);
      if (result >= min_value && result <= max_value) {
        return result;
      }
    } catch (NumberFormatException e) {
      // fall through to failure
    }

    String errMsg = "'" + arg + "' must be a positive integer";
    if (min_value > 1 || max_value < Integer.MAX_VALUE) {
      errMsg += " from " + min_value + " to " + max_value;
    }
    throw new IllegalArgumentException(errMsg);
  }

  /**
   * Not instantiable.
   */
  private ArgumentProcessor() {
  }

}
